import type { Metadata } from 'next'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { t } from '@/i18n'; import { api, isNotBuilt, isNotFound, safe } from '@/lib/api'; import { apiAnalytics } from '@/lib/api-analytics'; import { apiExplore } from '@/lib/api-explore'; import { regionShort } from '@/lib/regions'; import { jsonLd, jsonLdString, seoTitle } from '@/lib/seo'; import { routes } from '@/lib/site'; import { HEADLINE_TOPIC, type HEADLINE_INDICATORS } from '@/lib/topics'; import type { CountryResponse, FormatSpec } from '@/lib/types'; import { CountryHeader } from '@/components/country/country-header'; import { CountryStory } from '@/components/country/country-story'; import { DnaPanel } from '@/components/country/dna-panel'; import { KeyFacts } from '@/components/country/key-facts'; import { SimilarPanel } from '@/components/country/similar-panel'; import { Timeline } from '@/components/country/timeline'; import { CountryTopicsGrid } from '@/components/country/topics-grid'; import { ChangeList } from '@/components/data/change-list'; import { NotBuiltState } from '@/components/data/empty-state'; import { Metric, MetricGrid } from '@/components/data/metric'; import { Section } from '@/components/data/section'; import { TopicNav } from '@/components/data/topic-nav'; export const revalidate = 900; type Params = { slug: string }; async function loadCountry(slug: string): Promise { try { return await api.country(slug); } catch (e) { if (isNotFound(e)) return null; if (isNotBuilt(e)) return 'not-built'; throw e; } } export async function generateMetadata({ params }: { params: Promise }): Promise { const { slug } = await params; const data = await loadCountry(slug); if (!data || data === 'not-built') return { title: t('country.notFound'), robots: { index: false } }; const name = data.country.name ?? slug; const title = seoTitle.country(name); const description = t('country.description', { name }); const canonical = routes.country(data.country.slug ?? slug); return { title: { absolute: `${title} | ${t('site.name')}` }, description, alternates: { canonical }, openGraph: { title: `${title} | ${t('site.name')}`, description, url: canonical, type: 'article' }, twitter: { card: 'summary_large_image', title, description }, }; } export default async function CountryPage({ params }: { params: Promise }) { const { slug } = await params; const data = await loadCountry(slug); if (data === null) notFound(); if (data === 'not-built') return ; const c = data.country; const id = c.id; const name = c.name ?? id; const countryRef = { id, slug: c.slug ?? slug, name, flag: c.flag }; // Optional panels in parallel; each tolerates failure independently. const [changes, similar, insights, dna, events, story, quality, indicators] = await Promise.all([ safe(api.countryChanges(id, 8)), safe(api.countrySimilar(id, 'overall', 8)), safe(api.countryInsights(id)), safe(api.countryDna(id)), safe(api.countryEvents(id, 120)), safe(apiAnalytics.countryStory(id)), safe(apiAnalytics.countryQuality(id)), safe(apiExplore.indicators({ with_data: true })), ]); const counts = Object.fromEntries(data.topics.map((tp) => [tp.id, tp.n_with_data])); const withData = data.topics.reduce((a, tp) => a + tp.n_with_data, 0); const formats: Record = Object.fromEntries((indicators?.items ?? []).map((i) => [i.slug, { format: i.format, unit: i.unit, unit_short: i.unit_short, precision: i.precision, name: i.short_name ?? i.name, higher_is_better: i.higher_is_better }])); const ld = [jsonLd.country({ slug: c.slug ?? slug, name, iso3: c.iso3 ?? id, capital: c.capital }), jsonLd.breadcrumbs([{ name: t('nav.countries'), path: routes.countries() }, { name, path: routes.country(c.slug ?? slug) }])]; return ( <>